---
title: "05-Spring AI构建MCP服务端实战"
created: 2026-05-14
aliases:
- Spring AI构建MCP服务端实战
tags:
- 项目
---
# Spring AI构建MCP服务端实战
## 实战目标
这一篇我们动手搭建一个真实可用的MCP Server。为了让示例更有实际意义,我们以"智能办公助手"为场景,开发以下工具:
| 工具名称 | 功能描述 |
| --- | --- |
| checkAttendance | 查询员工考勤记录 |
| bookMeetingRoom | 预订会议室 |
| queryRoomSchedule | 查询会议室排期 |
我们会分别用Stdio、SSE、Streamable HTTP三种模式来部署这个Server,让你对比体会它们的差异。
## 技术栈选择
Spring AI从1.0版本开始提供MCP Server的Boot Starter,主要有三个:
| Starter | 传输模式 | 适用场景 |
| --- | --- | --- |
| spring-ai-starter-mcp-server | Stdio | 本地工具,Claude Desktop/Cursor插件 |
| spring-ai-starter-mcp-server-webmvc | Streamable HTTP | 远程服务,基于Spring MVC |
| spring-ai-starter-mcp-server-webflux | Streamable HTTP | 远程服务,基于响应式WebFlux |
本文使用webmvc版本演示(用得最多),Stdio模式只需换依赖和配置即可。
## 示例中项目地址
- 项目模块:`ai-example-spring-ai-office-mcp-server`
### Maven依赖
```xml
org.springframework.ai
spring-ai-starter-mcp-server-webmvc
${spring-ai.version}
```
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/05-MCP详细解析/assets/FkRpckYVh6LzTped4fYmeNegb3So-55d8f29e.png]]
## 开发工具类
### 考勤查询工具
```java
@Service
public class AttendanceTools {
/**
* 查询员工考勤记录
* 大模型会根据description判断何时使用此工具
*/
@Tool(description = "查询员工的考勤记录,包括出勤天数、迟到次数、早退次数、请假天数。" +
"当用户询问考勤、打卡、出勤等相关问题时使用此工具。")
public String checkAttendance(
@ToolParam(description = "员工工号,如E10086") String employeeId,
@ToolParam(description = "查询月份,格式YYYY-MM,如2025-03") String month) {
// 实际项目中这里调用HR系统API
// 这里用模拟数据演示
return String.format("""
{
"employeeId": "%s",
"month": "%s",
"workDays": 22,
"actualDays": 21,
"lateTimes": 2,
"earlyLeaveTimes": 0,
"leaveDays": 1,
"overtimeHours": 8.5
}
""", employeeId, month);
}
/**
* 员工签到打卡
*/
@Tool(description = "员工进行签到打卡操作。当用户说要打卡、签到时使用此工具。")
public String clockIn(
@ToolParam(description = "员工工号") String employeeId,
@ToolParam(description = "打卡类型:IN表示上班签到,OUT表示下班签退") String type) {
String currentTime = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
String status = "IN".equalsIgnoreCase(type) ? "上班签到" : "下班签退";
return String.format("""
{
"success": true,
"employeeId": "%s",
"clockType": "%s",
"clockTime": "%s",
"message": "%s成功"
}
""", employeeId, type, currentTime, status);
}
}
```
### 会议室管理工具
```java
@Service
public class MeetingRoomTools {
/**
* 查询会议室排期
*/
@Tool(description = "查询指定会议室在某天的预订情况和空闲时段。" +
"当用户想知道会议室是否有空、什么时候可以用时,使用此工具。")
public String queryRoomSchedule(
@ToolParam(description = "会议室编号,如A301、B502") String roomId,
@ToolParam(description = "查询日期,格式YYYY-MM-DD") String date) {
// 模拟返回会议室排期
return String.format("""
{
"roomId": "%s",
"roomName": "第%s会议室",
"date": "%s",
"capacity": 10,
"bookedSlots": [
{"start": "09:00", "end": "10:30", "subject": "产品需求评审", "organizer": "张经理"},
{"start": "14:00", "end": "15:00", "subject": "技术方案讨论", "organizer": "李工"}
],
"availableSlots": [
{"start": "10:30", "end": "12:00"},
{"start": "13:00", "end": "14:00"},
{"start": "15:00", "end": "18:00"}
]
}
""", roomId, roomId, date);
}
/**
* 预订会议室
*/
@Tool(description = "预订会议室。当用户说要订会议室、约会议室时使用此工具。")
public String bookMeetingRoom(
@ToolParam(description = "会议室编号") String roomId,
@ToolParam(description = "预订日期,格式YYYY-MM-DD") String date,
@ToolParam(description = "开始时间,格式HH:mm") String startTime,
@ToolParam(description = "结束时间,格式HH:mm") String endTime,
@ToolParam(description = "会议主题") String subject,
@ToolParam(description = "预订人姓名") String organizer) {
// 模拟预订成功
String bookingId = "BK" + System.currentTimeMillis();
return String.format("""
{
"success": true,
"bookingId": "%s",
"roomId": "%s",
"date": "%s",
"timeSlot": "%s - %s",
"subject": "%s",
"organizer": "%s",
"message": "会议室预订成功"
}
""", bookingId, roomId, date, startTime, endTime, subject, organizer);
}
/**
* 取消会议室预订
*/
@Tool(description = "取消已预订的会议室。当用户说要取消会议、退订会议室时使用此工具。")
public String cancelBooking(
@ToolParam(description = "预订单号") String bookingId) {
return String.format("""
{
"success": true,
"bookingId": "%s",
"message": "预订已取消"
}
""", bookingId);
}
}
```
### 注册工具到MCP Server
```java
@Configuration
public class McpServerConfig {
/**
* 注册考勤工具
*/
@Bean
public ToolCallbackProvider attendanceToolProvider(AttendanceTools attendanceTools) {
return MethodToolCallbackProvider.builder()
.toolObjects(attendanceTools)
.build();
}
/**
* 注册会议室工具
*/
@Bean
public ToolCallbackProvider meetingRoomToolProvider(MeetingRoomTools meetingRoomTools) {
return MethodToolCallbackProvider.builder()
.toolObjects(meetingRoomTools)
.build();
}
}
```
`MethodToolCallbackProvider`会自动扫描工具类中的`@Tool`注解方法,提取方法名作为工具名、description作为工具描述、方法参数和`@ToolParam`作为参数定义。
> `description` 的质量直接决定大模型能否正确选中这个工具。描述要说清楚"什么情况下使用",而不只是"这个工具做什么"。例如加上"当用户询问考勤、打卡、出勤等相关问题时使用此工具"这样的触发条件描述效果更好。
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/05-MCP详细解析/assets/Fk-MXv9mxs2C1-s_H5K_me155cEn-5ab725e0.png]]
### 启动类
```java
@SpringBootApplication
public class ExampleSpringAiOfficeMcpServerApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleSpringAiOfficeMcpServerApplication.class, args);
}
}
```
## 配置三种传输模式
### Streamable HTTP模式(推荐)
这是远程部署的推荐方式:
```yaml
server:
port: 7090
spring:
ai:
mcp:
server:
name: office-tools-server
version: 1.0.0
type: SYNC
protocol: STREAMABLE
streamable-http:
mcp-endpoint: /mcp
keep-alive-interval: 30s
```
配置说明:
| 配置项 | 说明 |
| --- | --- |
| server.port | HTTP服务端口 |
| spring.ai.mcp.server.name | Server名称,Client连接时可见 |
| spring.ai.mcp.server.version | Server版本号 |
| spring.ai.mcp.server.type | 执行模式,SYNC同步/ASYNC异步 |
| spring.ai.mcp.server.protocol | 传输协议,STREAMABLE表示Streamable HTTP |
| streamable-http.mcp-endpoint | MCP服务端点路径 |
| streamable-http.keep-alive-interval | 心跳间隔,保持连接活跃 |
启动后,MCP服务地址为:`http://localhost:7090/mcp`
### SSE模式(了解即可)
如果需要兼容老版本Client:
```yaml
server:
port: 7090
spring:
ai:
mcp:
server:
name: office-tools-server
version: 1.0.0
type: SYNC
sse-endpoint: /sse
sse-message-endpoint: /messages
```
SSE模式有两个端点:
- `/sse`:Client建立长连接监听消息
- `/messages`:Client发送请求
### Stdio模式
Stdio模式需要换依赖:
```xml
org.springframework.ai
spring-ai-starter-mcp-server
${spring-ai.version}
```
配置文件也要调整:
```yaml
spring:
main:
web-application-type: none # 关闭Web容器
banner-mode: off # 关闭Banner,避免干扰通信
ai:
mcp:
server:
name: office-tools-server
version: 1.0.0
type: SYNC
stdio: true
# 关闭所有日志输出到控制台
logging:
level:
root: OFF
file:
name: ./logs/mcp-server.log # 日志写入文件
```
**重要提示**:Stdio模式下,stdout是MCP通信通道,任何非协议内容(日志、Banner)都会导致通信失败。必须关闭控制台输出。
> Stdio 模式以 stdout 作为 MCP 通信通道。任何写入 stdout 的非 JSON-RPC 内容(Spring Banner、日志、`System.out.println`)都会破坏协议帧,导致 Client 报 JSON 解析错误。必须设置 `banner-mode: off` 和 `logging.level.root: OFF`,日志改为写入文件。
打包后的使用方式:
```bash
# 打包
mvn clean package -DskipTests
# Client配置里这样引用
java -jar ai-example-spring-ai-office-mcp-server-0.0.1-SNAPSHOT.jar
```
## 测试验证
### 使用MCP Inspector测试
MCP官方提供了一个可视化调试工具——MCP Inspector。
安装和启动:
```text
# 需要Node.js环境
npx @modelcontextprotocol/inspector@latest
```
启动后访问 `http://localhost:6274`,会看到一个Web界面。
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/05-MCP详细解析/assets/FgJxIBDPqeVO4HGL9FLQCaReWmGu-f6eb047e.png]]
**测试Streamable HTTP模式**:
- Transport Type选择"Streamable"
- URL填入:`http://localhost:7090/mcp`
- 点击Connect
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/05-MCP详细解析/assets/Fq_EaLgaEI_3owL-WukAx8APYI6_-7c4823bf.png]]
连接成功后,点击Tools标签页,可以看到我们注册的所有工具。
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/05-MCP详细解析/assets/FpEtzCwcMeW2I6-FoQKMZ_zeOqJ4-58bc2ee8.png]]
选择一个工具,填入参数,点击Run Tool即可测试。
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/05-MCP详细解析/assets/FtjoFA_Yn3vHd5WmJSdv2s86cNQg-977a89dd.png]]
**测试Stdio模式**:
- Transport Type选择"Stdio"
- Command填入Java路径
- Args填入:`-jar /path/to/office-mcp-server-1.0.0.jar`
### 使用Cursor/Cline测试
在Cursor的MCP配置中添加:
```json
{
"mcpServers": {
"office-tools": {
"type": "streamableHttp",
"url": "http://localhost:7090/mcp"
}
}
}
```
配置成功后,在Cursor中对话:
```text
用户:帮我查一下工号E10086这个月的考勤
```
Cursor会自动调用`checkAttendance`工具并返回结果。
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/05-MCP详细解析/assets/Fn4w8ool9agOELL05iNUH_YzvRKi-b8b06c04.png]]
## 进阶:复杂参数处理
上面的例子参数都是简单的String类型。实际项目中,经常需要处理复杂的对象参数。
### 使用POJO作为参数
定义请求对象:
```java
package com.example.office.model;
import org.springframework.ai.tool.annotation.ToolParam;
public class BookingRequest {
@ToolParam(description = "会议室编号")
private String roomId;
@ToolParam(description = "预订日期,格式YYYY-MM-DD")
private String date;
@ToolParam(description = "开始时间,格式HH:mm")
private String startTime;
@ToolParam(description = "结束时间,格式HH:mm")
private String endTime;
@ToolParam(description = "会议主题")
private String subject;
@ToolParam(description = "参会人员列表")
private List attendees;
// getter/setter省略
}
```
工具方法改为:
```java
@Tool(description = "预订会议室,支持指定参会人员")
public String bookMeetingRoomAdvanced(BookingRequest request) {
// 处理逻辑
return "预订成功";
}
```
这样大模型在调用时,会根据`@ToolParam`的描述来填充各个字段。
> 当工具参数超过 3 个时,推荐将参数封装成 POJO,并为每个字段加上 `@ToolParam` 描述。这比写多个平铺参数更清晰,大模型也更容易理解整体语义。返回值同样可以用 POJO,框架会自动序列化成 JSON。
### 使用POJO作为返回值
返回值也可以是对象,框架会自动序列化成JSON:
```java
@Tool(description = "查询会议室信息")
public RoomInfo getRoomInfo(String roomId) {
RoomInfo info = new RoomInfo();
info.setRoomId(roomId);
info.setCapacity(10);
info.setFacilities(Arrays.asList("投影仪", "白板", "视频会议设备"));
return info;
}
```
## 常见问题排查
### 问题一:Stdio模式连接失败
**现象**:Client报JSON解析错误
**原因**:控制台有非JSON内容输出
**解决**:
- 确保`banner-mode: off`
- 确保`logging.level.root: OFF`
- 检查代码里有没有`System.out.println`
### 问题二:工具没有被注册
**现象**:Client连接成功但看不到工具
**原因**:工具类没有被Spring扫描到,或者`@Tool`注解漏写
**解决**:
- 确保工具类有`@Service`或`@Component`注解
- 确保工具方法有`@Tool`注解
- 确保`McpServerConfig`中注册了工具Provider
### 问题三:Streamable HTTP返回404
**现象**:访问`/mcp`返回404
**原因**:配置的endpoint路径和实际访问的不一致
**解决**:检查`streamable-http.mcp-endpoint`配置,确保访问路径正确
### 问题四:中文乱码
**现象**:返回的中文显示为乱码
**原因**:字符编码不是UTF-8
**解决**:添加配置
```yaml
server:
servlet:
encoding:
charset: UTF-8
force: true
enabled: true
```
## 小结
这一篇我们完成了MCP Server的实战开发:
- **项目搭建**:使用Spring AI的MCP Server Starter
- **工具开发**:用`@Tool`和`@ToolParam`注解定义工具
- **工具注册**:通过`MethodToolCallbackProvider`注册到MCP Server
- **三种模式配置**:Streamable HTTP(推荐)、SSE、Stdio
- **测试验证**:使用MCP Inspector和Cursor测试
记住几个关键点:
- 远程部署用Streamable HTTP
- 本地插件用Stdio
- Stdio模式必须关闭控制台输出
- 工具的description要写清楚,这是大模型判断的依据
**三种模式的 Starter 选择**
- **Stdio 本地工具**:`spring-ai-starter-mcp-server`
- **远程服务(Spring MVC)**:`spring-ai-starter-mcp-server-webmvc`
- **远程服务(响应式)**:`spring-ai-starter-mcp-server-webflux`
切换模式只需换依赖和修改配置,工具代码完全不变。
下一篇我们来看MCP Client端的开发,学习如何在自己的应用中集成MCP工具能力。
---
**企业级项目导航**:⬅️ [[04-Spring AI MCP客户端开发指南|04-Spring AI MCP客户端开发指南]] | 05-Spring AI构建MCP服务端实战 | ➡️ [[06-三种传输模式全面解读|06-三种传输模式全面解读]]